﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

namespace SS2TC.Speech
{
    /// <summary>
    /// Reads the raw binary data from a specified audio stream.
    /// This class does not filter the input stream.
    /// </summary>
    public class AudioStreamReader : AudioStream
    {
        private IntPtr waveInPointer = new IntPtr();

        private uint result;

        public AudioStreamReader(WaveFormat waveFormat) : base (waveFormat)
        {
        }

        /// <summary>
        /// Opens the audio input stream.
        /// </summary>
        protected override void Open()
        {
            uint result = NativeMethods.waveInOpen(
                ref waveInPointer,
                NativeMethods.WAVE_MAPPER,
                ref nativeWaveFormat,
                IntPtr.Zero,
                0,
                (uint)NativeMethods.WaveInOpenFlags.WAVE_FORMAT_DIRECT);

            if (result != (uint)NativeMethods.MMRESULT.MMSYSERR_NOERROR)
            {
                throw new InvalidOperationException("There was an error opening wave binaryWriter");
            }
        }

        /// <summary>
        /// Closes the input stream.
        /// </summary>
        public override void Close()
        {
            //IntPtr pointerToWaveInHeader = GetIntPtr(waveHeader);

            //while (NativeMethods.waveInUnprepareHeader(waveInPointer, pointerToWaveInHeader, (int)waveHeaderSize)
            //    == (int)NativeMethods.MMRESULT.WAVERR_STILLPLAYING)
            //{
            //}

            while (NativeMethods.waveInClose(waveInPointer) != (uint)NativeMethods.MMRESULT.MMSYSERR_NOERROR)
            {
            }
        }

        /// <summary>
        /// Retrieves the unfiltered binary data of the stream.
        /// </summary>
        /// <param name="seconds">Length of the stream.</param>
        /// <returns>Raw stream data</returns>
        public byte[] Read(uint seconds)
        {
            Open();

            waveData = new byte[nativeWaveFormat.nAvgBytesPerSec * seconds];
            IntPtr dataPointer = GetIntPtr(waveData);

            waveHeader.dwBufferLength = nativeWaveFormat.nAvgBytesPerSec * seconds;
            waveHeader.lpData = dataPointer;
            waveHeader.dwFlags = 0;
            waveHeader.dwLoops = 0;
            waveHeader.dwBytesRecorded = 0;
            waveHeader.dwUser = IntPtr.Zero;
            waveHeader.lpNext = IntPtr.Zero;
            waveHeader.reserved = IntPtr.Zero;

            // Header size in bytes
            uint waveHeaderSize = Convert.ToUInt32(Marshal.SizeOf(waveHeader));

            if (NativeMethods.waveInPrepareHeader(waveInPointer, ref waveHeader, waveHeaderSize)
                != NativeMethods.MMRESULT.MMSYSERR_NOERROR)
            {
                throw new InvalidOperationException("There was an error preparing the header");
            }

            result = NativeMethods.waveInAddBuffer(waveInPointer, ref waveHeader, waveHeaderSize);

            if (result != (uint)NativeMethods.MMRESULT.MMSYSERR_NOERROR)
            {
                throw new InvalidOperationException("There was an error buffering");
            }

            if (NativeMethods.waveInStart(waveInPointer) != (uint)NativeMethods.MMRESULT.MMSYSERR_NOERROR)
            {
                throw new InvalidOperationException("There was an error starting recording");
            }

            Thread.Sleep(TimeSpan.FromSeconds(seconds));

            return waveData;
        }
    }
}
